﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Interop;
using System.Windows.Shapes;
using System.Threading;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using Interpreter;
using Microsoft.Win32;

namespace LogoScriptIDE
{
    /// <summary>
    /// CanvasWindow.xaml 的交互逻辑
    /// Canvas Window
    /// </summary>
    public partial class CanvasWindow : Window
    {
        /// <summary>
        /// Construct a new CanvasWindow
        /// </summary>
        public CanvasWindow()
        {
            InitializeComponent();
            Left = 600;
            Top = 48;

            try
            {
                // Create a new Turtle for this
                Turtle = new Turtle(this);
                m_modified = true;
                MouseClicked = false;

                // Set up D3D Canvas
                HRESULT.Check(SetSize(640, 480));
                HRESULT.Check(SetAlpha(false));
                HRESULT.Check(SetNumDesiredSamples(4));

                // Set up D3D Rendering
                CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
            }
            catch (Exception e)
            {
                HRESULT.Check(Marshal.GetHRForException(e));
            }
            
            // Setup Turtle Geometry
            m_turtleCircle = new Ellipse();
            m_turtleCircle.Stroke = Brushes.Green;
            m_turtleCircle.StrokeThickness = 2;
            m_turtleCircle.Fill = Brushes.LightGreen;
            m_turtleCircle.Width = 16;
            m_turtleCircle.Height = 16;
            VisualTreeHelper.GetOffset(m_turtleCircle);
            ui_layout.Children.Add(m_turtleCircle);

            m_turtleLine = new Line();
            m_turtleLine.Stroke = Brushes.Green;
            m_turtleLine.StrokeThickness = 2;
            ui_layout.Children.Add(m_turtleLine);
        }

        /// <summary>
        /// CanvasWindow dtor
        /// </summary>
        ~CanvasWindow()
        {
            // Release D3D Canvas
            Destroy();
        }

        private Ellipse m_turtleCircle;
        private Line m_turtleLine;

        /// <summary>
        /// Main rendering function
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">Rendering Event Arguments</param>
        void CompositionTarget_Rendering(object sender, EventArgs e)
        {
            RenderingEventArgs args = (RenderingEventArgs)e;

            //Render Trutle
            Point pos = m_turtle.Pos;
            m_turtleCircle.Visibility = m_turtle.Show ? Visibility.Visible : Visibility.Hidden;
            m_turtleLine.Visibility = m_turtleCircle.Visibility;
            Canvas.SetTop(m_turtleCircle, pos.Y - 8);
            Canvas.SetLeft(m_turtleCircle, pos.X - 8);
            m_turtleLine.X1 = pos.X;
            m_turtleLine.Y1 = pos.Y;
            m_turtleLine.X2 = pos.X + 8 * Math.Cos(m_turtle.Angle / 180.0 * Math.PI);
            m_turtleLine.Y2 = pos.Y + 8 * Math.Sin(m_turtle.Angle / 180.0 * Math.PI);

            // Render User-Drawn Content
            // It's possible for Rendering to call back twice in the same frame 
            // so only render when we haven't already rendered in this frame.
            // If user-drawn content have not been modified since last rendering
            // Skip rendering as well to save CPU resource
            if (m_modified && d3dimg.IsFrontBufferAvailable && _lastRender != args.RenderingTime)
            {
                IntPtr pSurface = IntPtr.Zero;
                //Rendering D3D at back buffer
                HRESULT.Check(GetBackBufferNoRef(out pSurface));
                if (pSurface != IntPtr.Zero)
                {
                    d3dimg.Lock();
                    // Repeatedly calling SetBackBuffer with the same IntPtr is 
                    // a no-op. There is no performance penalty.
                    d3dimg.SetBackBuffer(D3DResourceType.IDirect3DSurface9, pSurface);
                    HRESULT.Check(Render());
                    d3dimg.AddDirtyRect(new Int32Rect(0, 0, d3dimg.PixelWidth, d3dimg.PixelHeight));
                    d3dimg.Unlock();

                    // Indicate last rendering time
                    _lastRender = args.RenderingTime;
                    m_modified = false;
                }

            }
        }

        /// <summary>
        /// User-drawn content has been modified since last rendering
        /// </summary>
        private bool m_modified;

        /// <summary>
        /// The only Turtle object(Controls CanvasWindow to draw)
        /// </summary>
        private Turtle m_turtle;
        public Turtle Turtle
        {
            get
            {
                return m_turtle;
            }
            set
            {
                m_turtle = value;
            }
        }

        /// <summary>
        /// Draw a single Line, simply redirected to D3D Library
        /// </summary>
        /// <param name="start">The start position of the line</param>
        /// <param name="end">The end position of the line</param>
        /// <param name="color">Line color,using 0xAARRGGBB format</param>
        /// <param name="width">Line width</param>
        public void DrawLine(Point start, Point end,uint color,float width)
        {
            HRESULT.Check(AddLine(new POINT(start), new POINT(end), color, width));
        }

        /// <summary>
        /// Draw a line of text, simply redirected to D3D Library
        /// </summary>
        /// <param name="pos">The anchor(top-left) psoition of the text</param>
        /// <param name="color">Text color, using 0xAARRGGBB format</param>
        /// <param name="text">Text buffer, not longer than 256 chacters</param>
        public void DrawText(Point pos, uint color, string text)
        {
            HRESULT.Check(AddText(new POINT(pos), color, text));
        }

        /// <summary>
        /// Set background color( wiping user-drawn content)
        /// </summary>
        /// <param name="color">Background color, using 0xAARRGGBB format</param>
        public void SetCanvasBackgroundColor(uint color)
        {
            HRESULT.Check(SetBackgroundColor(color));
        }

        /// <summary>
        /// Setting modified flag
        /// </summary>
        public void SetModified()
        {
            m_modified = true;
        }

        /// <summary>
        /// Update now regargless the modified flag
        /// Stuck until a frame has been redrawn
        /// </summary>
        public void UpdateNow()
        {
            m_modified = true;
            while (m_modified == true)
                System.Threading.Thread.Sleep(1);
        }

        /// <summary>
        /// Resetting Canvas, wiping all user-drawn content
        /// </summary>
        /// <param name="newWidth">New width of canvas</param>
        /// <param name="newHeight">New height of canvas</param>
        public void ResetCanvas(double newWidth, double newHeight)
        {
            m_width = (int)newWidth;
            m_height = (int)newHeight;

            // Set size of the image element 
            imgelt.Width = newWidth;
            imgelt.Height = newHeight;
            ui_layout.Width = newWidth;
            ui_layout.Height = newHeight;

            MouseClicked = false;

            // Clear D3D Back buffer, set its size
            HRESULT.Check(SetSize((uint)m_width, (uint)m_height));
            HRESULT.Check(Clear());
        }

        /// <summary>
        /// Canvas Width
        /// </summary>
        public double canvasWidth
        {
            get { return m_width; }
        }
        private int m_width;

        /// <summary>
        /// Canvas Height
        /// </summary>
        public double canvasHeight
        {
            get { return m_height; }
        }
        private int m_height;

        /// <summary>
        /// Prevent Window from closing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            Hide();
            e.Cancel = true;
        }

        #region D3D Library API & InterOP
        /// <summary>
        /// Render and return back buffer
        /// </summary>
        /// <param name="pSurface">Back buffer</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int GetBackBufferNoRef(out IntPtr pSurface);

        /// <summary>
        /// Setting canvas size
        /// </summary>
        /// <param name="width">Canvas width</param>
        /// <param name="height">Canvas height</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int SetSize(uint width, uint height);

        /// <summary>
        /// Setting if use alpha
        /// </summary>
        /// <param name="useAlpha">if use alpha</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int SetAlpha(bool useAlpha);

        /// <summary>
        /// Setting desired number of samples(AA)
        /// </summary>
        /// <param name="numSamples">desired number of samples</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int SetNumDesiredSamples(uint numSamples);

        /// <summary>
        /// Float POINT structure
        /// </summary>
        [StructLayout(LayoutKind.Sequential)]
        struct POINT
        {
            public POINT(Point p)
            {
                x = (float)p.X;
                y = (float)p.Y;
            }

            public float x;
            public float y;
        }

        /// <summary>
        /// Set the adapter(video card) to use
        /// </summary>
        /// <param name="screenSpacePoint">A point in the window</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int SetAdapter(POINT screenSpacePoint);

        /// <summary>
        /// Render immediately
        /// </summary>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int Render();

        /// <summary>
        /// Destory D3D Resources
        /// </summary>
        [DllImport("LSD3D.dll")]
        static extern void Destroy();

        /// <summary>
        /// Clear the canvas
        /// </summary>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int Clear();

        /// <summary>
        /// Draw a line
        /// </summary>
        /// <param name="s">Line start point</param>
        /// <param name="e">Line end point</param>
        /// <param name="color">Line color</param>
        /// <param name="width">Line width</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int AddLine(POINT s,POINT e,uint color,float width);

        /// <summary>
        /// Set color of background, wiping any contents
        /// </summary>
        /// <param name="color">Backgroung color</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll")]
        static extern int SetBackgroundColor(uint color);

        /// <summary>
        /// Draw a line of text at given positon
        /// </summary>
        /// <param name="pos">Text top-left position</param>
        /// <param name="color">Text color</param>
        /// <param name="sText">Text buffer</param>
        /// <returns>HRESULT</returns>
        [DllImport("LSD3D.dll", CharSet = CharSet.Ansi)]
        static extern int AddText(POINT pos,uint color,[MarshalAs(UnmanagedType.LPStr)] string sText);

        [DllImport("kernel32.dll")]
        static extern void ExitProcess(uint ExitCode);

        TimeSpan _lastRender;

        public static class HRESULT
        {
            [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)]
            public static void Check(int hr)
            {
                if (hr < 0) //Failed
                {
                    MessageBox.Show(string.Format("Error 0x{0} occured in DirectX library, make sure you have DirectX 9.0c installed, and have a DirectX 9 video card with drivers properly installed.",((uint)hr).ToString("x8"))
                        ,"Error occured",MessageBoxButton.OK,MessageBoxImage.Error);
                    ExitProcess(0);
                }
                Marshal.ThrowExceptionForHR(hr);
            }
        }

        #endregion

        #region MouseClick
        /// <summary>
        /// Last clicked position
        /// </summary>
        private Point m_lastMouseClickPosition;
        public Point LastMouseClickPosition { get { return m_lastMouseClickPosition; } }
        private bool MouseClicked { get; set; }

        private void ui_layout_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            m_lastMouseClickPosition = new Point(e.GetPosition(ui_layout).X, e.GetPosition(ui_layout).Y);
            MouseClicked = true;
            MainWindow owner = (MainWindow)Owner;
            // Generate PU/PD Commands
            if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
            {
                owner.CanvasClick(
                    m_lastMouseClickPosition,
                    Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift));
            }
        }

        /// <summary>
        /// Get Last Mouse Input position
        /// </summary>
        /// <returns>Last position mouse clicked since last called, if not clicked returns (-1,-1)</returns>
        public Point GetMouseInput()
        {
            if (!MouseClicked)
                return new Point(-1,-1);
            MouseClicked = false;
            return m_lastMouseClickPosition;
        }
        #endregion

        #region Saving
        /// <summary>
        /// 右键菜单保存按钮回调函数
        /// </summary>
        private void ui_mnuSave_Click(object sender, RoutedEventArgs e)
        {
            Save();
        }

        /// <summary>
        /// 保存图片
        /// </summary>
        public void Save()
        {
            try
            {
                // 显示保存对话框
                SaveFileDialog saveFileDialog = new SaveFileDialog();
                saveFileDialog.Filter = "Bitmap files (.bmp)|*.bmp";
                saveFileDialog.FileName = "canvas.bmp";
                saveFileDialog.DefaultExt = ".bmp";

                // 若用户选择保存文件，则进行保存
                if (saveFileDialog.ShowDialog() == true)
                {
                    // 将当前画布内容画到一个DrawingVisual上
                    DrawingVisual dv = new DrawingVisual();
                    DrawingContext ctx = dv.RenderOpen();
                    ctx.DrawImage(imgelt.Source, new Rect(0, 0, imgelt.Width, imgelt.Height));
                    
                    // 绘制海龟
                    if (m_turtle.Show)
                    {
                        ctx.DrawEllipse(
                            m_turtleCircle.Fill,
                            new Pen(m_turtleCircle.Stroke, m_turtleCircle.StrokeThickness),
                            m_turtle.Pos,
                            m_turtleCircle.Width / 2,
                            m_turtleCircle.Height / 2);

                        Point st = m_turtle.Pos;
                        Point ed = new Point(
                            st.X + 8 * Math.Cos(m_turtle.Angle / 180.0 * Math.PI),
                            st.Y + 8 * Math.Sin(m_turtle.Angle / 180.0 * Math.PI));

                        ctx.DrawLine(
                            new Pen(m_turtleLine.Stroke, m_turtleLine.StrokeThickness),
                            st, ed);
                    }
                    ctx.Close();

                    // 渲染DrawingVisual到RenterTargetBitmap上
                    RenderTargetBitmap rtb = new RenderTargetBitmap((int)imgelt.Width, (int)imgelt.Height, 0, 0, PixelFormats.Pbgra32);
                    rtb.Render(dv);

                    // 将渲染好的RenterTargetBitmap转换为Bitmap类型
                    System.Drawing.Bitmap bmp = new System.Drawing.Bitmap((int)imgelt.Width, (int)imgelt.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
                    System.Drawing.Imaging.BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(System.Drawing.Point.Empty, bmp.Size), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
                    rtb.CopyPixels(Int32Rect.Empty, data.Scan0, data.Height * data.Stride, data.Stride);

                    // 保存该Bitmap到文件
                    bmp.UnlockBits(data);
                    bmp.Save(saveFileDialog.FileName);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("FAILED TO SAVE FILE :\n" + ex.Message);
            }
        }
        #endregion

        /// <summary>
        /// 鼠标移动回调函数
        /// </summary>
        private void ui_layout_MouseMove(object sender, MouseEventArgs e)
        {
            Point pos = e.GetPosition(imgelt);
            // 更新状态栏信息
            if (pos.X < 0 || pos.Y < 0 ||
                pos.X > ui_layout.Width || pos.Y > ui_layout.Height)
            {
                ui_MousePosition.Text = "Out of canvas.";
            }
            else
            {
                ui_MousePosition.Text = "X: " + (int)pos.X;
                ui_MousePosition.Text += ", Y: " + (int)pos.Y;
            }
        }

        /// <summary>
        /// 编辑器编辑状态发生变化回调函数（由MainWindow调用）
        /// </summary>
        /// <param name="isReadOnly">是否只读</param>
        public void OnEditorIsReadOnlyChanged(bool isReadOnly)
        {
            // 若编辑器只读，则右键代码生成不可用
            if (isReadOnly)
            {
                ui_mnuSetxy.IsEnabled = false;
                ui_mnuSetxyPU.IsEnabled = false;
            }
            else
            {
                ui_mnuSetxy.IsEnabled = true;
                ui_mnuSetxyPU.IsEnabled = true;
            }
        }

        /// <summary>
        /// 生成SETXY代码回调函数
        /// </summary>
        private void ui_mnuSetxy_Click(object sender, RoutedEventArgs e)
        {
            // 将鼠标信息发送给MainWindow，再由MainWindow来调用EditorWindow
            ((MainWindow)Owner).CanvasClick(
                    contextClickPosition, false);
        }

        /// <summary>
        /// 生成PU SETXY PD代码回调函数
        /// </summary>
        private void ui_mnuSetxyPU_Click(object sender, RoutedEventArgs e)
        {
            // 将鼠标信息发送给MainWindow，再由MainWindow来调用EditorWindow
            ((MainWindow)Owner).CanvasClick(
                    contextClickPosition, true);
        }

        private Point contextClickPosition;

        /// <summary>
        /// 右键菜单打开回调函数
        /// </summary>
        private void ScrollViewer_ContextMenuOpening(object sender, ContextMenuEventArgs e)
        {
            // 获得鼠标相对位置
            Point p = Mouse.GetPosition(imgelt);
            p.X = (int)p.X;
            p.Y = (int)p.Y;
            contextClickPosition = p;

            // 设置按钮标签
            ui_mnuSetxy.Header = String.Format("Generate SETXY({0}, {1});", p.X, p.Y);
            ui_mnuSetxyPU.Header = String.Format("Generate PU(); SETXY({0}, {1}); PD();", p.X, p.Y);
        }
    }
}
